Last active
September 18, 2024 10:19
-
-
Save trueroad/cc8bc24b703e7024b80ea517bc23f9dd to your computer and use it in GitHub Desktop.
Test cairo overwrite notehead.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
\version "2.24.3" | |
%%% INCLUDE %%% | |
\pointAndClickOff | |
upper = \relative | |
{ | |
\clef treble | |
\key c \major | |
\time 4/4 | |
\tempo "Allegro" 4 = 96 | |
r16_\mf c'( d e f d e c g'8) c b^\prall c | | |
d16 g,( a b c a b g d'8) g f^\prall g | | |
e16 a g f e g f a g f e d c e d f | | |
} | |
lower = \relative | |
{ | |
\clef bass | |
\key c \major | |
\time 4/4 | |
r2 r16 c( d e f d e c | | |
g'8) g, r4 r16 g'( a b c a b g | | |
c8) b( c d e) g,( a b) | | |
} | |
\score | |
{ | |
\new PianoStaff | |
<< | |
\new Staff = "upper" \upper | |
\new Staff = "lower" \lower | |
>> | |
\layout {} | |
% \midi {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# LilyPond で生成した楽譜の符頭にバツ印やテキストを上書きしてみるテスト | |
# | |
# | |
# 動作環境 | |
# LilyPond 2.24.3 | |
# Python 3.9 | |
# python-cairo, pypdf | |
# Poppler | |
# GNU make, sed, etc. | |
# | |
# 使い方 | |
# `make` で SVG が生成される。 | |
# .html をブラウザで開くと楽譜の符頭に上書きされたものが見える。 | |
# | |
# | |
# ターゲットの拡張子なしファイル名 | |
# | |
TARGET_STEM = invention1 | |
# | |
# 同梱の .ly をそのまま使用して生成するファイル | |
# | |
# PDF 経由で生成する SVG | |
CROPPED_PDF_SVG = $(TARGET_STEM).cropped.pdf.svg | |
# PDF 経由せず直接生成する SVG | |
CROPPED_SVG = $(TARGET_STEM).cropped.svg | |
# | |
# ポイント&クリックを有効にした(アノテーションを含んだ) .ly と生成ファイル | |
# | |
# .ly | |
ANNOT_LY = $(TARGET_STEM).annot.ly | |
# リンクなどアノテーションを含んだ PDF | |
ANNOT_CROPPED_PDF = $(TARGET_STEM).annot.cropped.pdf | |
# PDF の CropBox とリンク情報のテキストファイル | |
LINK_TXT = $(TARGET_STEM).annot.cropped.link.txt | |
# | |
# SMF (.mid) を出力する .ly と生成ファイル | |
# | |
# .ly | |
MIDI_LY = $(TARGET_STEM).midi.ly | |
# SMF (.mid) | |
MIDI_MID = $(TARGET_STEM).midi.mid | |
# | |
# 音楽イベントを出力する .ly と生成ファイル | |
# | |
# .ly | |
EVENT_LY = $(TARGET_STEM).event.ly | |
# 音楽イベント一覧のテキストファイル | |
NOTES_TEXT = $(TARGET_STEM).event-unnamed-staff.notes | |
# | |
# 符頭に上書きする SVG | |
# | |
# バツ印 | |
CROSS_SVG = $(TARGET_STEM).overwrite-notehead.cross.svg | |
# テキスト(英文) | |
CROSS_TEXT_ENGLISH = $(TARGET_STEM).overwrite-notehead.text.english.svg | |
# テキスト(和文) | |
CROSS_TEXT_JAPANESE = $(TARGET_STEM).overwrite-notehead.text.japanese.svg | |
TARGET = $(CROPPED_PDF_SVG) $(CROPPED_SVG) \ | |
$(ANNOT_LY) $(ANNOT_CROPPED_PDF) $(LINK_TXT) \ | |
$(MIDI_LY) $(MIDI_MID) \ | |
$(EVENT_LY) $(NOTES_TEXT) \ | |
$(CROSS_SVG) $(CROSS_TEXT_ENGLISH) $(CROSS_TEXT_JAPANESE) | |
all: $(TARGET) | |
.PHONY: all clean | |
SED = sed | |
LILYPOND = lilypond | |
PDFTOCAIRO = pdftocairo | |
SHOW_PDF_LINK = ./show_pdf_link.py | |
TEST_CAIRO_OVERWRITE_NOTEHEAD = ./test-cairo-overwrite-notehead.py | |
target: $(TARGET) | |
clean: | |
$(RM) *~ $(TARGET) | |
# PDF から SVG を生成する | |
# | |
# Poppler 付属の pdftocairo を使用する。 | |
# 文字はすべてアウトライン化される。 | |
# 寸法や位置関係など PDF と完全一致した SVG が出力される。 | |
# リンク等は消滅する。 | |
%.cropped.pdf.svg: %.cropped.pdf | |
$(PDFTOCAIRO) -svg $< $@ | |
# LilyPond でクロップされた SVG を直接出力する | |
# | |
# 非クロップ版 SVG も出力されるので削除する。 | |
# | |
# LilyPond 2.24.3 では PDF 出力と SVG 出力ではバックエンドの違いからか | |
# クロップ範囲が微妙に異なるようで微妙にズレてしまう。 | |
# `Allegro` や `( = 96)` 等の文字は文字として出力され | |
# フォントの違いなどからかそもそもの配置が異なる。 | |
# 音楽記号は音符休符などだけでなく拍子記号の `C` や強弱記号の `mf` も含め、 | |
# アウトライン化されたパスとして出力され、相対的な位置関係は一致する。 | |
# ただし、文字中の四分音符などは形状こそ一致するものの、 | |
# 文字の配置がことなるためか他の音楽記号とは位置がズレる。 | |
# cairo バックエンドを持つ LilyPond ならば PDF, SVG 双方とも | |
# 同じ cairo バックエンドで出力すれば完全一致するのかもしれないが | |
# LilyPond 2.24.3 ではデフォルト無効なので通常は使用できない。 | |
%.cropped.svg: %.ly | |
$(LILYPOND) -dcrop --svg $< | |
$(RM) $*.svg | |
# LilyPond でクロップされた PDF を出力する | |
# | |
# PNG と非クロップ版 PDF も出力されてしまうので削除する。 | |
%.cropped.pdf: %.ly | |
$(LILYPOND) -dcrop --pdf $< | |
$(RM) $*.cropped.png $*.pdf | |
# LilyPond で SMF (.mid) を出力する | |
# | |
# Linux や Cygwin ではデフォルト拡張子が .midi なので .mid を指定する。 | |
%.mid: %.ly | |
$(LILYPOND) -dmidi-extension=mid $< | |
# LilyPond で音楽イベントを出力する | |
# | |
# 譜の名前を付けていないのでファイル名に `unnamed` が付く。 | |
# 出力が上書きではなく追記になってしまうので一旦出力ファイルを消す。 | |
# `\layout {}` が無くて補完されてしまい PDF が出力されるので削除する。 | |
%-unnamed-staff.notes: %.ly | |
$(RM) $@ | |
$(LILYPOND) $< | |
$(RM) $*.pdf | |
# ポイント&クリックを有効にした .ly を生成する | |
%.annot.ly: %.ly | |
$(SED) -r \ | |
-e 's/\\pointAndClickOff/\\pointAndClickOn/' \ | |
$< > $@ | |
# PDF を出力せずに SMF (.mid) を出力する .ly を生成する | |
%.midi.ly: %.ly | |
$(SED) -r \ | |
-e 's/\\layout \{\}/% \\layout \{\}/' \ | |
-e 's/% \\midi \{\}/\\midi \{\}/' \ | |
$< > $@ | |
# 音楽イベントを出力し(event-listener.ly をインクルードする) | |
# ポイント&クリックを有効にし、 | |
# PDF を出力しない .ly を生成する。 | |
%.event.ly: %.ly | |
$(SED) -r \ | |
-e 's/%%% INCLUDE %%%/\\include "event-listener.ly"/' \ | |
-e 's/\\pointAndClickOff/\\pointAndClickOn/' \ | |
-e 's/\\layout \{\}/% \\layout \{\}/' \ | |
$< > $@ | |
# PDF の CropBox とリンク情報を出力する | |
%.link.txt: %.pdf | |
$(SHOW_PDF_LINK) $< > $@ | |
# バツ印の SVG を生成する | |
%.overwrite-notehead.cross.svg: %.annot.cropped.link.txt \ | |
%.event-unnamed-staff.notes | |
$(TEST_CAIRO_OVERWRITE_NOTEHEAD) --cross $^ $@ | |
# テキスト(英文)の SVG を生成する | |
%.overwrite-notehead.text.english.svg: %.annot.cropped.link.txt \ | |
%.event-unnamed-staff.notes | |
$(TEST_CAIRO_OVERWRITE_NOTEHEAD) --text "Too long" $^ $@ | |
# テキスト(和文)の SVG を生成する | |
%.overwrite-notehead.text.japanese.svg: %.annot.cropped.link.txt \ | |
%.event-unnamed-staff.notes | |
$(TEST_CAIRO_OVERWRITE_NOTEHEAD) --text "長すぎ" $^ $@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
""" | |
Show PDF link annots. | |
https://gist.github.com/trueroad/4810ab0845d86fb97510e4e3d3bbed13 | |
Copyright (C) 2024 Masamichi Hosoda. | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions | |
are met: | |
* Redistributions of source code must retain the above copyright notice, | |
this list of conditions and the following disclaimer. | |
* Redistributions in binary form must reproduce the above copyright notice, | |
this list of conditions and the following disclaimer in the documentation | |
and/or other materials provided with the distribution. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
ARE DISCLAIMED. | |
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
SUCH DAMAGE. | |
""" | |
import sys | |
from typing import cast, Optional, Union | |
from pypdf import PageObject, PdfReader | |
from pypdf.generic import (ArrayObject, DictionaryObject, | |
IndirectObject, PdfObject, RectangleObject) | |
def main() -> None: | |
"""Do main.""" | |
if len(sys.argv) != 2: | |
print('Usage: ./show_pdf_link.py [INPUT.PDF]', file=sys.stderr) | |
sys.exit(1) | |
reader: PdfReader = PdfReader(sys.argv[1]) | |
page: PageObject | |
for page in reader.pages: | |
cropbox: RectangleObject = page.cropbox | |
crop_left: float = cropbox.left.as_numeric() | |
crop_bottom: float = cropbox.bottom.as_numeric() | |
crop_right: float = cropbox.right.as_numeric() | |
crop_top: float = cropbox.top.as_numeric() | |
print(f'\nCropBox\t{crop_left}\t{crop_bottom}' | |
f'\t{crop_right}\t{crop_top}') | |
if '/Annots' in page: | |
annot: Union[IndirectObject, PdfObject] | |
for annot in cast(ArrayObject, page['/Annots']): | |
if type(annot) is not IndirectObject: | |
raise RuntimeError('It is not IndirectObject.') | |
obj: Optional[Union[DictionaryObject, PdfObject]] = \ | |
annot.get_object() | |
if obj is None: | |
raise RuntimeError('get_object() returns None.') | |
if type(obj) is not DictionaryObject: | |
raise RuntimeError('It is not DictionaryObject.') | |
if str(obj['/Subtype']) == '/Link': | |
a: Union[DictionaryObject, PdfObject] = obj['/A'] | |
if type(a) is not DictionaryObject: | |
raise RuntimeError('It is not DictionaryObject.') | |
if str(a['/S']) == '/URI': | |
rect: Union[ArrayObject, PdfObject] = obj['/Rect'] | |
if type(rect) is not ArrayObject: | |
raise RuntimeError('It is not ArrayObject.') | |
link_left: float = rect[0].as_numeric() | |
link_bottom: float = rect[1].as_numeric() | |
link_right: float = rect[2].as_numeric() | |
link_top: float = rect[3].as_numeric() | |
uri: str = str(a['/URI']) | |
print(f'Link\t{link_left}\t{link_bottom}' | |
f'\t{link_right}\t{link_top}' | |
f'\t{uri}') | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
""" | |
Test cairo overwrite notehead. | |
https://gist.github.com/trueroad/cc8bc24b703e7024b80ea517bc23f9dd | |
Copyright (C) 2024 Masamichi Hosoda. | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions | |
are met: | |
* Redistributions of source code must retain the above copyright notice, | |
this list of conditions and the following disclaimer. | |
* Redistributions in binary form must reproduce the above copyright notice, | |
this list of conditions and the following disclaimer in the documentation | |
and/or other materials provided with the distribution. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
ARE DISCLAIMED. | |
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
SUCH DAMAGE. | |
""" | |
from dataclasses import dataclass | |
import os | |
import sys | |
from typing import Any, Dict, Optional, TextIO, Union | |
import cairo | |
@dataclass(frozen=True) | |
class rect_container: | |
"""Rectangle container class.""" | |
left: float | |
top: float | |
right: float | |
bottom: float | |
@dataclass(frozen=True) | |
class point_and_click_container: | |
"""Point and click container class.""" | |
row: int | |
column: int | |
@dataclass(frozen=True) | |
class note_container: | |
"""Note container class.""" | |
time: float | |
noteno: int | |
point_and_click: point_and_click_container | |
class link_text: | |
"""Link text class.""" | |
def __init__(self) -> None: | |
"""__init__.""" | |
# PDF CropBox | |
self.cropbox: rect_container | |
# PDF links | |
self.links: dict[point_and_click_container, rect_container] = {} | |
# notes | |
self.notes: list[note_container] = [] | |
def load_link(self, filename: Union[str, bytes, os.PathLike[Any]] | |
) -> None: | |
"""Load link text.""" | |
f: TextIO | |
with open(filename, 'r') as f: | |
line: str | |
for line in f: | |
items: list[str] = line.split() | |
if len(items) == 5 and items[0] == 'CropBox': | |
self.cropbox = rect_container(left=float(items[1]), | |
bottom=float(items[2]), | |
right=float(items[3]), | |
top=float(items[4])) | |
elif len(items) == 6 and items[0] == 'Link': | |
if not items[5].startswith('textedit://'): | |
continue | |
pc_items: list[str] = items[5].split(':') | |
self.links[ | |
point_and_click_container( | |
row=int(pc_items[-3]), | |
column=int(pc_items[-2]))] = \ | |
rect_container( | |
left=float(items[1]), | |
bottom=float(items[2]), | |
right=float(items[3]), | |
top=float(items[4])) | |
def load_notes(self, filename: Union[str, bytes, os.PathLike[Any]] | |
) -> None: | |
"""Load notes.""" | |
f: TextIO | |
with open(filename, 'r') as f: | |
line: str | |
for line in f: | |
items: list[str] = line.split('\t') | |
if len(items) == 6 and items[1] == 'note': | |
pc_items: list[str] = items[5].split() | |
if len(pc_items) != 3 or \ | |
pc_items[0] != 'point-and-click': | |
raise RuntimeError('Notes file format error.') | |
self.notes.append(note_container( | |
time=float(items[0]), | |
noteno=int(items[2]), | |
point_and_click=point_and_click_container( | |
row=int(pc_items[2]), | |
column=int(pc_items[1])))) | |
def calc_size(self) -> tuple[float, float]: | |
""" | |
Calc SVG size. | |
Returns: | |
tuple[float, float]: SVG width (pt), height (pt) | |
""" | |
width: float = self.cropbox.right - self.cropbox.left | |
height: float = self.cropbox.top - self.cropbox.bottom | |
return (width, height) | |
def conv_axis(self, x: float, y: float) -> tuple[float, float]: | |
""" | |
Convert axis PDF to SVG. | |
Args: | |
x (float): PDF x axis (pt) | |
y (float): PDF y axis (pt) | |
Returns: | |
tuple[float, float]: SVG x axis (pt) ,y axis (pt) | |
""" | |
svg_x: float = x - self.cropbox.left | |
svg_y: float = self.cropbox.top - self.cropbox.bottom - y | |
return (svg_x, svg_y) | |
def conv_rect(self, rect: rect_container) -> rect_container: | |
""" | |
Convert rect PDF to SVG. | |
Args: | |
rect (rect_container): PDF rect | |
Returns: | |
rect_container: SVG rect | |
""" | |
svg_left: float | |
svg_top: float | |
svg_right: float | |
svg_bottom: float | |
svg_left, svg_top = self.conv_axis(rect.left, rect.top) | |
svg_right, svg_bottom = self.conv_axis(rect.right, rect.bottom) | |
return rect_container(left=svg_left, top=svg_top, | |
right=svg_right, bottom=svg_bottom) | |
def draw_crosses(self, context: cairo.Context) -> None: | |
"""Draw crosses.""" | |
nc: note_container | |
for nc in self.notes: | |
rect: rect_container = self.links[nc.point_and_click] | |
draw_cross(context, self.conv_rect(rect)) | |
def draw_texts(self, context: cairo.Context, text: str) -> None: | |
"""Draw texts.""" | |
nc: note_container | |
for nc in self.notes: | |
rect: rect_container = self.links[nc.point_and_click] | |
draw_text(context, self.conv_rect(rect), text) | |
def draw_cross(context: cairo.Context, rect: rect_container) -> None: | |
"""Draw cross.""" | |
context.move_to(rect.left, rect.top) | |
context.line_to(rect.right, rect.bottom) | |
context.move_to(rect.right, rect.top) | |
context.line_to(rect.left, rect.bottom) | |
def draw_text(context: cairo.Context, rect: rect_container, text: str) -> None: | |
"""Draw text.""" | |
context.select_font_face('sans-serif') | |
context.set_font_size(rect.bottom - rect.top) | |
# 指定した座標がベースラインの左端になる模様。 | |
# よくあるフォントはベースラインの上が 0.88 下が 0.12 あるので、 | |
# 符頭の上下ピッタリに合わせるには以下のようにする。 | |
# (和文フォントはこれでだいたい合うが欧文はフォントや環境次第) | |
context.move_to(rect.left, rect.top + (rect.bottom - rect.top) * 0.88) | |
context.show_text(text) | |
def main() -> None: | |
"""Do main.""" | |
print('Test cairo overwrite notehead\n' | |
'https://gist.github.com/trueroad/' | |
'cc8bc24b703e7024b80ea517bc23f9dd\n\n' | |
'Copyright (C) 2024 Masamichi Hosoda.\n' | |
'All rights reserved.\n') | |
import argparse | |
parser: argparse.ArgumentParser = argparse.ArgumentParser() | |
parser.add_argument('LINK.TXT', help='(in) Link file.') | |
parser.add_argument('STAFF.NOTES', help='(in) Notes file.') | |
parser.add_argument('OUTPUT.SVG', help='(out) Output SVG.') | |
parser.add_argument('--cross', help='Overwrite cross.', | |
action='store_true') | |
parser.add_argument('--text', help='Overwrite text.', | |
type=str, nargs=1) | |
args: argparse.Namespace = parser.parse_args() | |
vargs: Dict[str, Any] = vars(args) | |
link_filename: str = vargs['LINK.TXT'] | |
notes_filename: str = vargs['STAFF.NOTES'] | |
svg_filename: str = vargs['OUTPUT.SVG'] | |
b_cross: bool = vargs['cross'] | |
text_str: Optional[str] = None | |
if vargs['text'] is not None: | |
text_str = vargs['text'][0] | |
print(f'Link filename : {link_filename}\n' | |
f'Notes filename: {notes_filename}\n' | |
f'Output SVG : {svg_filename}\n' | |
f'Cross : {b_cross}\n' | |
f'Text : {text_str}\n') | |
lt: link_text = link_text() | |
lt.load_link(link_filename) | |
lt.load_notes(notes_filename) | |
width: float | |
height: float | |
width, height = lt.calc_size() | |
surface: cairo.SVGSurface | |
# ファイル名、横 pt 、縦 pt | |
with cairo.SVGSurface(svg_filename, width, height) as surface: | |
context: cairo.Context = cairo.Context(surface) | |
if b_cross: | |
context.set_line_width(2) | |
context.set_source_rgba(1, 0, 0, 0.7) | |
lt.draw_crosses(context) | |
context.stroke() | |
if text_str is not None: | |
context.set_source_rgba(1, 0, 0, 0.9) | |
lt.draw_texts(context, text_str) | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>テスト(バツ印)</title> | |
</head> | |
<style> | |
.relative | |
{ | |
position: relative; | |
} | |
.absolute | |
{ | |
position: absolute; | |
} | |
</style> | |
<body> | |
<h1>テスト(バツ印)</h1> | |
<div class="relative"> | |
<img src="invention1.cropped.pdf.svg" class="absolute" /> | |
<img src="invention1.overwrite-notehead.cross.svg" class="absolute" /> | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Test (text english)</title> | |
</head> | |
<style> | |
.relative | |
{ | |
position: relative; | |
} | |
.absolute | |
{ | |
position: absolute; | |
} | |
</style> | |
<body> | |
<h1>Test (text english)</h1> | |
<div class="relative"> | |
<img src="invention1.cropped.pdf.svg" class="absolute" /> | |
<img src="invention1.overwrite-notehead.text.english.svg" | |
class="absolute" /> | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>テスト(テキスト和文)</title> | |
</head> | |
<style> | |
.relative | |
{ | |
position: relative; | |
} | |
.absolute | |
{ | |
position: absolute; | |
} | |
</style> | |
<body> | |
<h1>テスト(テキスト和文)</h1> | |
<div class="relative"> | |
<img src="invention1.cropped.pdf.svg" class="absolute" /> | |
<img src="invention1.overwrite-notehead.text.japanese.svg" | |
class="absolute" /> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment