mirror of
https://github.com/penpot/penpot.git
synced 2025-05-10 11:06:38 +02:00
✨ Add more complete font conversion suite.
This commit is contained in:
parent
90aab92a59
commit
91fe0b0985
7 changed files with 171 additions and 12 deletions
|
@ -13,6 +13,8 @@
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.rlimits :as rlm]
|
[app.rlimits :as rlm]
|
||||||
[app.rpc.queries.svg :as svg]
|
[app.rpc.queries.svg :as svg]
|
||||||
|
[buddy.core.bytes :as bb]
|
||||||
|
[buddy.core.codecs :as bc]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.java.shell :as sh]
|
[clojure.java.shell :as sh]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
|
@ -64,7 +66,8 @@
|
||||||
|
|
||||||
(defmethod process-error :default
|
(defmethod process-error :default
|
||||||
[error]
|
[error]
|
||||||
(ex/raise :type :internal :cause error))
|
(ex/raise :type :internal
|
||||||
|
:cause error))
|
||||||
|
|
||||||
(defn run
|
(defn run
|
||||||
[{:keys [rlimits] :as cfg} {:keys [rlimit] :or {rlimit :image} :as params}]
|
[{:keys [rlimits] :as cfg} {:keys [rlimit] :or {rlimit :image} :as params}]
|
||||||
|
@ -232,6 +235,19 @@
|
||||||
(fs/slurp-bytes output-file))))
|
(fs/slurp-bytes output-file))))
|
||||||
|
|
||||||
|
|
||||||
|
(otf->ttf [data]
|
||||||
|
(let [input-file (fs/create-tempfile :prefix "penpot")
|
||||||
|
output-file (fs/path (str input-file ".ttf"))
|
||||||
|
_ (with-open [out (io/output-stream input-file)]
|
||||||
|
(IOUtils/writeChunked ^bytes data ^OutputStream out)
|
||||||
|
(.flush ^OutputStream out))
|
||||||
|
res (sh/sh "fontforge" "-lang=ff" "-c"
|
||||||
|
(str/fmt "Open('%s'); Generate('%s')"
|
||||||
|
(str input-file)
|
||||||
|
(str output-file)))]
|
||||||
|
(when (zero? (:exit res))
|
||||||
|
(fs/slurp-bytes output-file))))
|
||||||
|
|
||||||
(ttf-or-otf->woff [data]
|
(ttf-or-otf->woff [data]
|
||||||
(let [input-file (fs/create-tempfile :prefix "penpot" :suffix "")
|
(let [input-file (fs/create-tempfile :prefix "penpot" :suffix "")
|
||||||
output-file (fs/path (str input-file ".woff"))
|
output-file (fs/path (str input-file ".woff"))
|
||||||
|
@ -250,17 +266,68 @@
|
||||||
(.flush ^OutputStream out))
|
(.flush ^OutputStream out))
|
||||||
res (sh/sh "woff2_compress" (str input-file))]
|
res (sh/sh "woff2_compress" (str input-file))]
|
||||||
(when (zero? (:exit res))
|
(when (zero? (:exit res))
|
||||||
(fs/slurp-bytes output-file))))]
|
(fs/slurp-bytes output-file))))
|
||||||
|
|
||||||
|
(woff->sfnt [data]
|
||||||
|
(let [input-file (fs/create-tempfile :prefix "penpot" :suffix "")
|
||||||
|
_ (with-open [out (io/output-stream input-file)]
|
||||||
|
(IOUtils/writeChunked ^bytes data ^OutputStream out)
|
||||||
|
(.flush ^OutputStream out))
|
||||||
|
res (sh/sh "woff2sfnt" (str input-file)
|
||||||
|
:out-enc :bytes)]
|
||||||
|
(when (zero? (:exit res))
|
||||||
|
(:out res))))
|
||||||
|
|
||||||
|
;; Documented here:
|
||||||
|
;; https://docs.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
|
||||||
|
(get-sfnt-type [data]
|
||||||
|
(let [buff (bb/slice data 0 4)
|
||||||
|
type (bc/bytes->hex buff)]
|
||||||
|
(case type
|
||||||
|
"4f54544f" :otf
|
||||||
|
"00010000" :ttf
|
||||||
|
(ex/raise :type :internal
|
||||||
|
:code :unexpected-data
|
||||||
|
:hint "unexpected font data"))))
|
||||||
|
|
||||||
|
(gen-if-nil [val factory]
|
||||||
|
(if (nil? val)
|
||||||
|
(factory)
|
||||||
|
val))]
|
||||||
|
|
||||||
(let [current (into #{} (keys input))]
|
(let [current (into #{} (keys input))]
|
||||||
(if (contains? current "font/ttf")
|
(cond
|
||||||
(-> input
|
(contains? current "font/ttf")
|
||||||
(assoc "font/otf" (ttf->otf (get input "font/ttf")))
|
(let [data (get input "font/ttf")]
|
||||||
(assoc "font/woff" (ttf-or-otf->woff (get input "font/ttf")))
|
(-> input
|
||||||
(assoc "font/woff2" (ttf-or-otf->woff2 (get input "font/ttf"))))
|
(update "font/otf" gen-if-nil #(ttf->otf data))
|
||||||
|
(update "font/woff" gen-if-nil #(ttf-or-otf->woff data))
|
||||||
|
(assoc "font/woff2" (ttf-or-otf->woff2 data))))
|
||||||
|
|
||||||
(-> input
|
(contains? current "font/otf")
|
||||||
;; TODO: pending to implement
|
(let [data (get input "font/otf")]
|
||||||
;; (assoc "font/ttf" (otf->ttf (get input "font/ttf")))
|
(-> input
|
||||||
(assoc "font/woff" (ttf-or-otf->woff (get input "font/otf")))
|
(update "font/woff" gen-if-nil #(ttf-or-otf->woff data))
|
||||||
(assoc "font/woff2" (ttf-or-otf->woff2 (get input "font/otf"))))))))
|
(assoc "font/ttf" (otf->ttf data))
|
||||||
|
(assoc "font/woff2" (ttf-or-otf->woff2 data))))
|
||||||
|
|
||||||
|
(contains? current "font/woff")
|
||||||
|
(let [data (get input "font/woff")
|
||||||
|
sfnt (woff->sfnt data)]
|
||||||
|
(when-not sfnt
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :invalid-woff-file
|
||||||
|
:hint "invalid woff file"))
|
||||||
|
(let [stype (get-sfnt-type sfnt)]
|
||||||
|
(cond-> input
|
||||||
|
true
|
||||||
|
(-> (assoc "font/woff" data)
|
||||||
|
(assoc "font/woff2" (ttf-or-otf->woff2 sfnt)))
|
||||||
|
|
||||||
|
(= stype :otf)
|
||||||
|
(-> (assoc "font/otf" sfnt)
|
||||||
|
(assoc "font/ttf" (otf->ttf sfnt)))
|
||||||
|
|
||||||
|
(= stype :ttf)
|
||||||
|
(-> (assoc "font/otf" (ttf->otf sfnt))
|
||||||
|
(assoc "font/ttf" sfnt)))))))))
|
||||||
|
|
BIN
backend/tests/app/tests/_files/font-1.otf
Normal file
BIN
backend/tests/app/tests/_files/font-1.otf
Normal file
Binary file not shown.
BIN
backend/tests/app/tests/_files/font-1.ttf
Normal file
BIN
backend/tests/app/tests/_files/font-1.ttf
Normal file
Binary file not shown.
BIN
backend/tests/app/tests/_files/font-1.woff
Normal file
BIN
backend/tests/app/tests/_files/font-1.woff
Normal file
Binary file not shown.
BIN
backend/tests/app/tests/_files/font-2.otf
Normal file
BIN
backend/tests/app/tests/_files/font-2.otf
Normal file
Binary file not shown.
BIN
backend/tests/app/tests/_files/font-2.woff
Normal file
BIN
backend/tests/app/tests/_files/font-2.woff
Normal file
Binary file not shown.
92
backend/tests/app/tests/test_services_fonts.clj
Normal file
92
backend/tests/app/tests/test_services_fonts.clj
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.tests.test-services-fonts
|
||||||
|
(:require
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.http :as http]
|
||||||
|
[app.storage :as sto]
|
||||||
|
[app.tests.helpers :as th]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.test :as t]
|
||||||
|
[datoteka.core :as fs]))
|
||||||
|
|
||||||
|
(t/use-fixtures :once th/state-init)
|
||||||
|
(t/use-fixtures :each th/database-reset)
|
||||||
|
|
||||||
|
(t/deftest ttf-font-upload-1
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
proj-id (:default-project-id prof)
|
||||||
|
|
||||||
|
ttfdata (-> (io/resource "app/tests/_files/font-1.ttf")
|
||||||
|
(fs/slurp-bytes))
|
||||||
|
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
:profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id "custom-somefont"
|
||||||
|
:font-family "somefont"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/ttf" ttfdata}}
|
||||||
|
out (th/mutation! params)]
|
||||||
|
|
||||||
|
;; (th/print-result! out)
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(let [result (:result out)]
|
||||||
|
(t/is (uuid? (:id result)))
|
||||||
|
(t/is (uuid? (:ttf-file-id result)))
|
||||||
|
(t/is (uuid? (:otf-file-id result)))
|
||||||
|
(t/is (uuid? (:woff1-file-id result)))
|
||||||
|
(t/is (uuid? (:woff2-file-id result)))
|
||||||
|
(t/are [k] (= (get params k)
|
||||||
|
(get result k))
|
||||||
|
:team-id
|
||||||
|
:font-id
|
||||||
|
:font-family
|
||||||
|
:font-weight
|
||||||
|
:font-style))))
|
||||||
|
|
||||||
|
(t/deftest ttf-font-upload-2
|
||||||
|
(let [prof (th/create-profile* 1 {:is-active true})
|
||||||
|
team-id (:default-team-id prof)
|
||||||
|
proj-id (:default-project-id prof)
|
||||||
|
|
||||||
|
data (-> (io/resource "app/tests/_files/font-1.woff")
|
||||||
|
(fs/slurp-bytes))
|
||||||
|
|
||||||
|
params {::th/type :create-font-variant
|
||||||
|
:profile-id (:id prof)
|
||||||
|
:team-id team-id
|
||||||
|
:font-id "custom-somefont"
|
||||||
|
:font-family "somefont"
|
||||||
|
:font-weight 400
|
||||||
|
:font-style "normal"
|
||||||
|
:data {"font/woff" data}}
|
||||||
|
out (th/mutation! params)]
|
||||||
|
|
||||||
|
;; (th/print-result! out)
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(let [result (:result out)]
|
||||||
|
(t/is (uuid? (:id result)))
|
||||||
|
(t/is (uuid? (:ttf-file-id result)))
|
||||||
|
(t/is (uuid? (:otf-file-id result)))
|
||||||
|
(t/is (uuid? (:woff1-file-id result)))
|
||||||
|
(t/is (uuid? (:woff2-file-id result)))
|
||||||
|
(t/are [k] (= (get params k)
|
||||||
|
(get result k))
|
||||||
|
:team-id
|
||||||
|
:font-id
|
||||||
|
:font-family
|
||||||
|
:font-weight
|
||||||
|
:font-style))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue