diff --git a/.gitignore b/.gitignore index f3043b21..2c5c2f37 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ docs/src/main/mdoc/api docs/src/pages/**/*.png + ### SublimeText ### doodle.sublime-project # cache files for sublime text @@ -141,5 +142,6 @@ local.properties .metals .bloop project/metals.sbt +.vscode .sbt-hydra-history diff --git a/circle.png b/circle.png new file mode 100644 index 00000000..a7e359d8 Binary files /dev/null and b/circle.png differ diff --git a/docs/src/pages/algebra/basic-with-text.png b/docs/src/pages/algebra/basic-with-text.png index 4968ed72..a86ec174 100644 Binary files a/docs/src/pages/algebra/basic-with-text.png and b/docs/src/pages/algebra/basic-with-text.png differ diff --git a/docs/src/pages/directory.conf b/docs/src/pages/directory.conf index 11cef31f..749b8bd1 100644 --- a/docs/src/pages/directory.conf +++ b/docs/src/pages/directory.conf @@ -6,5 +6,6 @@ laika.navigationOrder = [ interact algebra effect - development + svg + development ] diff --git a/docs/src/pages/svg/README.md b/docs/src/pages/svg/README.md new file mode 100644 index 00000000..7b02e3b2 --- /dev/null +++ b/docs/src/pages/svg/README.md @@ -0,0 +1,63 @@ +# Doodle SVG + +Doodle SVG draws Doodle pictures to SVG, both in the browser using [Scala JS](https://scala-js.org/) and to files on the JVM. + +## Usage + +Firstly, bring everything into scope + +```scala +import doodle.svg._ +``` + +Now what you can do depends on whether you are running in the browser or on the JVM. + + +### Running in the Browser + +In the browser you can draw a picture to SVG. To do this, construct a `Frame` with the `id` of the DOM element where you'd like the picture drawn. + +For example, if you have the following element in your HTML + +``` html +
+``` + +then you can create a `Frame` referring to it with + +``` scala mdoc:silent +val frame = Frame("svg-root") +``` + +Now suppose you have a picture called `thePicture`. You can draw it using the frame you just created like so + +``` scala +thePicture.drawWithFrame(frame) +``` + +The rendered SVG will appear where the element is positioned on your web page. + + +## Running on the JVM + +On the JVM you can't draw SVG to the screen. Use the `java2d` backend for that instead. However you can write SVG output in the usual way. + + +## Examples + +The source for these examples is [in the repository](https://github.com/creativescala/doodle-svg/tree/main/examples/src/main/scala). + +### Concentric Circles +@:doodle("concentric-circles", "ConcentricCircles.draw") + +### Text Positioning +@:doodle("text-positioning", "TextPositioning.draw") + +### Doodle Logo +@:doodle("doodle-logo", "DoodleLogo.draw") + +### Pulsing Circle +@:doodle("pulsing-circle", "PulsingCircle.draw") + +### Parametric Spiral +@:doodle("parametric-spiral", "ParametricSpiral.draw") diff --git a/examples/js/src/main/scala/doodle/examples/ConcentricCircles.scala b/examples/js/src/main/scala/doodle/examples/ConcentricCircles.scala new file mode 100644 index 00000000..1f4d0b5a --- /dev/null +++ b/examples/js/src/main/scala/doodle/examples/ConcentricCircles.scala @@ -0,0 +1,39 @@ +/* + * Copyright 2015 Creative Scala + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package doodle +package svg + +import doodle.core._ +import doodle.svg._ +import doodle.syntax.all._ +import scala.scalajs.js.annotation._ +import cats.effect.unsafe.implicits.global + +@JSExportTopLevel("ConcentricCircles") +object ConcentricCircles { + def circles(count: Int): Picture[Unit] = + if (count == 0) Picture.circle(20).fillColor(Color.hsl(0.degrees, 0.7, 0.6)) + else + Picture + .circle(count.toDouble * 20.0) + .fillColor(Color.hsl((count * 15).degrees, 0.7, 0.6)) + .under(circles(count - 1)) + + @JSExport + def draw(mount: String) = + circles(7).drawWithFrame(Frame(mount)) +} diff --git a/examples/js/src/main/scala/doodle/examples/DoodleLogo.scala b/examples/js/src/main/scala/doodle/examples/DoodleLogo.scala new file mode 100644 index 00000000..3215e26d --- /dev/null +++ b/examples/js/src/main/scala/doodle/examples/DoodleLogo.scala @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Creative Scala + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package doodle +package svg + +import doodle.core._ +import doodle.core.font._ +import doodle.svg._ +import doodle.syntax.all._ +import scala.scalajs.js.annotation._ +import cats.effect.unsafe.implicits.global + +@JSExportTopLevel("DoodleLogo") +object DoodleLogo { + val font = + Font.defaultSansSerif.size(FontSize.points(22)).weight(FontWeight.bold) + val logo: Picture[Unit] = + (0.to(10)) + .map(i => + Picture + .text("Doodle SVG") + .font(font) + .fillColor(Color.hotpink.spin(10.degrees * i.toDouble)) + .at(i * 3.0, i * -3.0) + ) + .toList + .allOn + + @JSExport + def draw(mount: String) = + logo.drawWithFrame(Frame(mount)) +} diff --git a/examples/js/src/main/scala/doodle/examples/ParametricSpiral.scala b/examples/js/src/main/scala/doodle/examples/ParametricSpiral.scala new file mode 100644 index 00000000..8f8d394e --- /dev/null +++ b/examples/js/src/main/scala/doodle/examples/ParametricSpiral.scala @@ -0,0 +1,44 @@ +import doodle.core._ +import doodle.svg._ +import doodle.syntax.all._ +import scala.scalajs.js.annotation._ +import cats.effect.unsafe.implicits.global + +@JSExportTopLevel("ParametricSpiral") +object ParametricSpiral { + + def parametricSpiral(angle: Angle): Point = + Point((Math.exp(angle.toTurns) - 1) * 200, angle) + + def drawCurve( + points: Int, + marker: Point => Picture[Unit], + curve: Angle => Point + ): Picture[Unit] = { + // Angle.one is one complete turn. I.e. 360 degrees + val turn = Angle.one / points.toDouble + def loop(count: Int): Picture[Unit] = { + count match { + case 0 => + val pt = curve(Angle.zero) + marker(pt).at(pt) + case n => + val pt = curve(turn * count.toDouble) + marker(pt).at(pt).on(loop(n - 1)) + } + } + + loop(points) + } + + @JSExport + def draw(id: String): Unit = { + val marker = (point: Point) => + Picture + .circle(point.r * 0.125 + 7) + .fillColor(Color.red.spin(point.angle / 4.0)) + .noStroke + + drawCurve(20, marker, parametricSpiral _).drawWithFrame(Frame(id)) + } +} diff --git a/examples/js/src/main/scala/doodle/examples/PulsingCircle.scala b/examples/js/src/main/scala/doodle/examples/PulsingCircle.scala new file mode 100644 index 00000000..58e486f0 --- /dev/null +++ b/examples/js/src/main/scala/doodle/examples/PulsingCircle.scala @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Creative Scala + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package doodle +package svg + +import doodle.core._ +import doodle.svg._ +import doodle.syntax.all._ +import doodle.interact.syntax.all._ +import scala.scalajs.js.annotation._ +import cats.effect.unsafe.implicits.global + +@JSExportTopLevel("PulsingCircle") +object PulsingCircle { + def circle(count: Double): Picture[Unit] = + Picture + .circle(2 * count + 10) + .fillColor(Color.hsl((count * 5).degrees, 0.7, 0.6)) + + @JSExport + def draw(mount: String) = + 0.0 + .upToIncluding(72.0) + .map(c => circle(c)) + .forSteps(120) + .andThen(_ => 72.0.upToIncluding(0.0).map(c => circle(c)).forSteps(120)) + .repeatForever + .animate(Frame(mount).withSize(72 * 2 + 10, 72 * 2 + 10)) +} diff --git a/examples/js/src/main/scala/doodle/examples/TextPositioning.scala b/examples/js/src/main/scala/doodle/examples/TextPositioning.scala new file mode 100644 index 00000000..044f2036 --- /dev/null +++ b/examples/js/src/main/scala/doodle/examples/TextPositioning.scala @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Creative Scala + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package doodle +package svg + +import doodle.core._ +import doodle.core.font._ +import doodle.svg._ +import doodle.syntax.all._ +import scala.scalajs.js.annotation._ +import cats.effect.unsafe.implicits.global + +@JSExportTopLevel("TextPositioning") +object TextPositioning { + val font = Font.defaultSansSerif.size(FontSize.points(18)) + def text(string: String): Picture[Unit] = + Picture.text(string).font(font).fillColor(Color.hotpink) + + val textPositioning: Picture[Unit] = + text("Above") + .above( + text("Center").on(Picture.circle(100).strokeWidth(3.0)) + ) + .above(text("Below")) + + @JSExport + def draw(mount: String) = + textPositioning.drawWithFrame(Frame(mount)) +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..497f1bef --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "doodle", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "doodle", + "version": "1.0.0", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..cb24432a --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "doodle", + "version": "1.0.0", + "description": "Copyright [Noel Welsh](http://noelwelsh.com).", + "main": "tailwind.config.js", + "directories": { + "doc": "docs", + "example": "examples" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +}