Spaces:
Sleeping
Sleeping
admin
commited on
Commit
·
284d622
1
Parent(s):
95b6e87
update stuff
Browse files- demo.md +14 -0
- src/modules/bitly-api.min.py +243 -96
- style.css +56 -0
demo.md
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
replace:
|
3 |
+
- name: Taro Tanaka
|
4 |
+
- group: Google Inc.
|
5 |
+
- email: [email protected]
|
6 |
+
- phone: 090 1234 5678
|
7 |
+
- link: https://example.com
|
8 |
+
---
|
9 |
+
Name: {name}
|
10 |
+
Group: {group}
|
11 |
+
Contact:
|
12 |
+
- Email: {email}
|
13 |
+
- Phone: {phone}
|
14 |
+
- Link: {link}
|
src/modules/bitly-api.min.py
CHANGED
@@ -9,6 +9,11 @@ import shutil
|
|
9 |
import qrcode
|
10 |
from io import BytesIO
|
11 |
import time
|
|
|
|
|
|
|
|
|
|
|
12 |
import gradio as gr
|
13 |
|
14 |
description = f'''
|
@@ -23,7 +28,7 @@ For more information, see
|
|
23 |
BITLY_ACCESS_TOKEN = os.getenv('BITLY_ACCESS_TOKEN', None)
|
24 |
BITLY_API_URL = "https://api-ssl.bitly.com/v4"
|
25 |
|
26 |
-
class
|
27 |
def __init__(self, access_token=None, api_url=None):
|
28 |
self.access_token = access_token if access_token else BITLY_ACCESS_TOKEN
|
29 |
self.api_url = api_url if api_url else BITLY_API_URL
|
@@ -88,105 +93,228 @@ class ShortenUrl:
|
|
88 |
except Exception as e:
|
89 |
return None, None, {"error": str(e)}
|
90 |
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
markdown_links = text
|
98 |
-
all_json_responses = []
|
99 |
-
qr_svgs = []
|
100 |
-
qr_pngs = []
|
101 |
-
|
102 |
-
for idx, url in enumerate(urls):
|
103 |
-
shorten_url, shorten_response_json = None, None
|
104 |
-
png_file_name = f'qr_code_{idx}.png'
|
105 |
-
png_output_path = os.path.join(tmpd, png_file_name)
|
106 |
-
try:
|
107 |
-
if shorten:
|
108 |
-
shorten_url, shorten_response_json = bitly.shorten(url)
|
109 |
-
all_json_responses.append(shorten_response_json)
|
110 |
-
|
111 |
-
if shorten and shorten_url:
|
112 |
-
url_to_process = shorten_url
|
113 |
-
else:
|
114 |
-
url_to_process = url
|
115 |
-
|
116 |
-
if generate_qr and url_to_process:
|
117 |
-
if shorten_url:
|
118 |
-
# qr_html, qr_png, qr_json = bitly.generate_qr(url_to_process)
|
119 |
-
qr_html, qr_png, qr_json = bitly.generate_qr_local(url_to_process)
|
120 |
-
else:
|
121 |
-
qr_html, qr_png, qr_json = bitly.generate_qr_local(url_to_process)
|
122 |
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
else:
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
markdown_links = markdown_links.replace(
|
137 |
-
|
138 |
-
url, f'<img src="{png_file_name}" height="40") [{url}]({url}) '
|
139 |
)
|
140 |
-
|
141 |
-
qr_svgs.append(qr_html)
|
142 |
-
all_json_responses.append(qr_json)
|
143 |
else:
|
144 |
-
results.append(("Error
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
except Exception as e:
|
153 |
-
|
154 |
|
155 |
-
|
156 |
-
json_output_path = os.path.join(tmpd, 'response.json')
|
157 |
-
with open(json_output_path, 'w') as json_file:
|
158 |
-
json.dump(all_json_responses, json_file)
|
159 |
|
160 |
-
markdown_output_path = os.path.join(tmpd, 'links.md')
|
161 |
-
with open(markdown_output_path, 'w') as markdown_file:
|
162 |
-
markdown_file.write(markdown_links)
|
163 |
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
qr_svg_output_paths.append(svg_output_path)
|
170 |
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
|
189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
|
191 |
class Interface:
|
192 |
@staticmethod
|
@@ -220,11 +348,15 @@ with gr.Blocks() as demo:
|
|
220 |
access_token = gr.Textbox(label="Access Token", placeholder="***")
|
221 |
api_url = gr.Textbox(label="API base URL", placeholder="https://")
|
222 |
url_input = gr.TextArea(label="Enter Text with URLs")
|
|
|
223 |
with gr.Row():
|
224 |
shorten_checkbox = gr.Checkbox(label="Shorten URL", value=True)
|
225 |
qr_checkbox = gr.Checkbox(label="Generate QR Code", value=True)
|
226 |
submit_button = gr.Button("Process URLs", variant='primary')
|
227 |
-
|
|
|
|
|
|
|
228 |
with gr.Column():
|
229 |
file_output = gr.File(label="Download ZIP")
|
230 |
with gr.Accordion(open=True, label="Gallery"):
|
@@ -234,14 +366,29 @@ with gr.Blocks() as demo:
|
|
234 |
with gr.Accordion(open=False, label="Preview"):
|
235 |
preview_output = gr.TextArea(label="Raw Text")
|
236 |
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
241 |
|
242 |
submit_button.click(
|
243 |
-
shorten_and_generate_qr,
|
244 |
-
inputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
outputs=[preview_output, gallery_output, file_output, json_output]
|
246 |
)
|
247 |
|
|
|
9 |
import qrcode
|
10 |
from io import BytesIO
|
11 |
import time
|
12 |
+
import textwrap
|
13 |
+
import markdown
|
14 |
+
import frontmatter
|
15 |
+
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
16 |
+
from io import BytesIO
|
17 |
import gradio as gr
|
18 |
|
19 |
description = f'''
|
|
|
28 |
BITLY_ACCESS_TOKEN = os.getenv('BITLY_ACCESS_TOKEN', None)
|
29 |
BITLY_API_URL = "https://api-ssl.bitly.com/v4"
|
30 |
|
31 |
+
class UrlProcessor:
|
32 |
def __init__(self, access_token=None, api_url=None):
|
33 |
self.access_token = access_token if access_token else BITLY_ACCESS_TOKEN
|
34 |
self.api_url = api_url if api_url else BITLY_API_URL
|
|
|
93 |
except Exception as e:
|
94 |
return None, None, {"error": str(e)}
|
95 |
|
96 |
+
@staticmethod
|
97 |
+
def shorten_and_generate_qr(access_token, api_url, text, shorten, generate_qr, common_images, individual_images):
|
98 |
+
bitly = UrlProcessor(access_token, api_url)
|
99 |
+
urls = re.findall(r'http[s]?://\S+', text)
|
100 |
+
results = []
|
101 |
+
ts, tmpd = Interface.get_tempdir()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
|
103 |
+
markdown_links = text
|
104 |
+
all_json_responses = []
|
105 |
+
qr_svgs = []
|
106 |
+
qr_pngs = []
|
107 |
+
card_htmls = []
|
108 |
+
|
109 |
+
for idx, url in enumerate(urls):
|
110 |
+
shorten_url, shorten_response_json = None, None
|
111 |
+
try:
|
112 |
+
if shorten:
|
113 |
+
shorten_url, shorten_response_json = bitly.shorten(url)
|
114 |
+
all_json_responses.append(shorten_response_json)
|
115 |
+
|
116 |
+
if shorten and shorten_url:
|
117 |
+
url_to_process = shorten_url
|
118 |
else:
|
119 |
+
url_to_process = url
|
120 |
+
|
121 |
+
if generate_qr and url_to_process:
|
122 |
+
if shorten_url:
|
123 |
+
qr_html, qr_png, qr_json = bitly.generate_qr_local(url_to_process)
|
124 |
+
else:
|
125 |
+
qr_html, qr_png, qr_json = bitly.generate_qr_local(url_to_process)
|
126 |
+
|
127 |
+
if qr_png:
|
128 |
+
qr_png_path = os.path.join(tmpd, f'qr_code_{idx}.png')
|
129 |
+
with open(qr_png_path, 'wb') as f:
|
130 |
+
f.write(qr_png)
|
131 |
+
markdown_links = markdown_links.replace(
|
132 |
+
url, f'<img src="{qr_png_path}" height="40"> [{url_to_process}]({url}) '
|
133 |
+
)
|
134 |
+
qr_pngs.append(qr_png_path)
|
135 |
+
qr_svgs.append(qr_html)
|
136 |
+
all_json_responses.append(qr_json)
|
137 |
+
else:
|
138 |
+
results.append(("Error generating QR code", url_to_process))
|
139 |
+
elif generate_qr:
|
140 |
+
qr_html, qr_png, qr_json = bitly.generate_qr_local(url)
|
141 |
+
if qr_png:
|
142 |
+
qr_png_path = os.path.join(tmpd, f'qr_code_{idx}.png')
|
143 |
+
with open(qr_png_path, 'wb') as f:
|
144 |
+
f.write(qr_png)
|
145 |
+
markdown_links = markdown_links.replace(
|
146 |
+
url, f'<img src="{qr_png_path}" height="40"> [{url}]({url}) '
|
147 |
+
)
|
148 |
+
qr_pngs.append(qr_png_path)
|
149 |
+
qr_svgs.append(qr_html)
|
150 |
+
all_json_responses.append(qr_json)
|
151 |
+
else:
|
152 |
+
results.append(("Error generating QR code", url))
|
153 |
+
elif shorten_url:
|
154 |
markdown_links = markdown_links.replace(
|
155 |
+
url, f'[{shorten_url}]({url}) '
|
|
|
156 |
)
|
157 |
+
results.append((None, shorten_url))
|
|
|
|
|
158 |
else:
|
159 |
+
results.append(("Error shortening URL", url))
|
160 |
+
except Exception as e:
|
161 |
+
results.append((str(e), url))
|
162 |
+
|
163 |
+
layer = ImageProcessor.create_layer()
|
164 |
+
|
165 |
+
for idx, png in enumerate(qr_pngs):
|
166 |
+
try:
|
167 |
+
png = ImageProcessor.combine_images(layer, [png])
|
168 |
+
if common_images:
|
169 |
+
png = ImageProcessor.combine_images(png, common_images)
|
170 |
+
if individual_images:
|
171 |
+
for im in individual_images:
|
172 |
+
png = ImageProcessor.combine_images(png, [im.name])
|
173 |
+
with open(png, "rb") as image_file:
|
174 |
+
encoded_string = base64.b64encode(image_file.read()).decode()
|
175 |
+
|
176 |
+
card_html = f'<img src="data:image/png;base64,{encoded_string}" alt="Card"> '
|
177 |
+
card_htmls.append(card_html)
|
178 |
+
except Exception as e:
|
179 |
+
raise Exception(f"Error combining images: {e}")
|
180 |
+
|
181 |
+
|
182 |
+
try:
|
183 |
+
json_output_path = os.path.join(tmpd, 'response.json')
|
184 |
+
with open(json_output_path, 'w') as json_file:
|
185 |
+
json.dump(all_json_responses, json_file)
|
186 |
+
|
187 |
+
markdown_output_path = os.path.join(tmpd, 'links.md')
|
188 |
+
with open(markdown_output_path, 'w') as markdown_file:
|
189 |
+
markdown_file.write(markdown_links)
|
190 |
+
|
191 |
+
qr_svg_output_paths = []
|
192 |
+
for idx, svg in enumerate(qr_svgs):
|
193 |
+
svg_output_path = os.path.join(tmpd, f'qr_code_{idx}.html')
|
194 |
+
with open(svg_output_path, 'w') as svg_file:
|
195 |
+
svg_file.write(svg)
|
196 |
+
qr_svg_output_paths.append(svg_output_path)
|
197 |
+
|
198 |
+
qr_png_output_paths = []
|
199 |
+
for idx, png in enumerate(qr_pngs):
|
200 |
+
qr_png_output_paths.append(png)
|
201 |
+
|
202 |
+
card_output_paths = []
|
203 |
+
for idx, card_html in enumerate(card_htmls):
|
204 |
+
card_output_path = os.path.join(tmpd, f'card_{idx}.html')
|
205 |
+
with open(card_output_path, 'w') as card_file:
|
206 |
+
card_file.write(card_html)
|
207 |
+
card_output_paths.append(card_output_path)
|
208 |
+
|
209 |
+
index_html = '<html><head><link rel="stylesheet" type="text/css" href="style.css"></head><body>'
|
210 |
+
for card_html in card_htmls:
|
211 |
+
index_html += card_html
|
212 |
+
index_html += '</body></html>'
|
213 |
+
|
214 |
+
index_output_path = os.path.join(tmpd, 'index.html')
|
215 |
+
with open(index_output_path, 'w') as index_file:
|
216 |
+
index_file.write(index_html)
|
217 |
+
|
218 |
+
zip_output_path = os.path.join(tmpd, f"{ts}.zip")
|
219 |
+
zip_content_list = [
|
220 |
+
json_output_path,
|
221 |
+
markdown_output_path,
|
222 |
+
*qr_svg_output_paths,
|
223 |
+
*qr_png_output_paths,
|
224 |
+
*card_output_paths,
|
225 |
+
index_output_path
|
226 |
+
]
|
227 |
+
zip_output_path = Interface.create_zip(zip_content_list, zip_output_path)
|
228 |
except Exception as e:
|
229 |
+
return f"An error occurred: {str(e)}", None, None, []
|
230 |
|
231 |
+
return markdown_links, qr_png_output_paths, zip_output_path, all_json_responses
|
|
|
|
|
|
|
232 |
|
|
|
|
|
|
|
233 |
|
234 |
+
class TextProcessor:
|
235 |
+
def apply_frontmatter(text, replacements):
|
236 |
+
for key, value in replacements.items():
|
237 |
+
text = text.replace(f'{{{key}}}', value)
|
238 |
+
return text
|
|
|
239 |
|
240 |
+
def apply_style(input_markdown_path, style_css_path):
|
241 |
+
with open(input_markdown_path, 'r') as file:
|
242 |
+
md_content = file.read()
|
243 |
+
|
244 |
+
fm = frontmatter.load(input_markdown_path)
|
245 |
+
replacements = {key: list(item.values())[0] \
|
246 |
+
for item in fm.metadata['replace'] for key in item}
|
247 |
+
processed_text = TextProcessor.apply_frontmatter(md_content, replacements)
|
248 |
+
|
249 |
+
with open(style_css_path, 'r') as file:
|
250 |
+
style_css = file.read()
|
251 |
+
|
252 |
+
html_content = markdown.markdown(processed_text)
|
253 |
+
html_content = f'<html><head><style>{style_css}</style></head><body>{html_content}</body></html>'
|
254 |
+
|
255 |
+
temp_html_path = os.path.join(tempfile.gettempdir(), 'processed.html')
|
256 |
+
with open(temp_html_path, 'w') as file:
|
257 |
+
file.write(html_content)
|
258 |
+
|
259 |
+
return temp_html_path
|
260 |
+
|
261 |
+
class ImageProcessor:
|
262 |
+
|
263 |
+
|
264 |
+
def create_layer(layout='horizontal', size=(91 * 3.7795, 55 * 3.7795)):
|
265 |
+
size = (int(size[0]), int(size[1])) # Convert to integers
|
266 |
+
img = Image.new('RGBA', size, (255, 255, 255, 0))
|
267 |
+
draw = ImageDraw.Draw(img)
|
268 |
+
if layout == 'vertical':
|
269 |
+
draw.rectangle([(0, 0), (size[0], size[1])], outline="black", width=3)
|
270 |
+
elif layout == 'horizontal':
|
271 |
+
draw.rectangle([(0, 0), (size[1], size[0])], outline="black", width=3)
|
272 |
|
273 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmpfile:
|
274 |
+
img.save(tmpfile.name, format="PNG")
|
275 |
+
return tmpfile.name
|
276 |
+
|
277 |
+
@staticmethod
|
278 |
+
def combine_images(base_image_path, overlay_image_paths):
|
279 |
+
with open(base_image_path, 'rb') as f:
|
280 |
+
base_img = Image.open(f).convert("RGBA")
|
281 |
+
for overlay_path in overlay_image_paths:
|
282 |
+
with open(overlay_path, 'rb') as f:
|
283 |
+
overlay_img = Image.open(f).convert("RGBA")
|
284 |
+
# overlay_img = overlay_img.resize(base_img.size, Image.NEAREST )
|
285 |
+
base_img = ImageOps.contain(overlay_img, base_img.size)
|
286 |
+
|
287 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmpfile:
|
288 |
+
base_img.save(tmpfile.name, format="PNG")
|
289 |
+
return tmpfile.name
|
290 |
+
|
291 |
+
# @staticmethod
|
292 |
+
# def insert_txt(png_path, text):
|
293 |
+
# img = Image.open(png_path)
|
294 |
+
# draw = ImageDraw.Draw(img)
|
295 |
+
# font = ImageFont.load_default() # Update to a specific font if needed
|
296 |
+
# width, height = img.size
|
297 |
+
# text_width, text_height = draw.textlength(text, font=font)
|
298 |
+
# text_position = (width - text_width - 10, height - text_height - 10)
|
299 |
+
# draw.text(text_position, text, font=font, fill="black")
|
300 |
+
|
301 |
+
# with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmpfile:
|
302 |
+
# img.save(tmpfile.name, format="PNG")
|
303 |
+
# return tmpfile.name
|
304 |
+
|
305 |
+
# @staticmethod
|
306 |
+
# def insert_txt(png_path, text):
|
307 |
+
# img = Image.open(png_path)
|
308 |
+
# draw = ImageDraw.Draw(img)
|
309 |
+
# font = ImageFont.load_default() # Update to a specific font if needed
|
310 |
+
# width, height = img.size
|
311 |
+
# text_width, text_height = draw.textlength(text, font=font)
|
312 |
+
# text_position = (width - text_width - 10, height - text_height - 10)
|
313 |
+
# draw.text(text_position, text, font=font, fill="black")
|
314 |
+
|
315 |
+
# with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmpfile:
|
316 |
+
# img.save(tmpfile.name, format="PNG")
|
317 |
+
# return tmpfile.name
|
318 |
|
319 |
class Interface:
|
320 |
@staticmethod
|
|
|
348 |
access_token = gr.Textbox(label="Access Token", placeholder="***")
|
349 |
api_url = gr.Textbox(label="API base URL", placeholder="https://")
|
350 |
url_input = gr.TextArea(label="Enter Text with URLs")
|
351 |
+
|
352 |
with gr.Row():
|
353 |
shorten_checkbox = gr.Checkbox(label="Shorten URL", value=True)
|
354 |
qr_checkbox = gr.Checkbox(label="Generate QR Code", value=True)
|
355 |
submit_button = gr.Button("Process URLs", variant='primary')
|
356 |
+
with gr.Accordion(open=False, label="Insert"):
|
357 |
+
with gr.Row():
|
358 |
+
cm_img_input = gr.Files(label='Insert common images')
|
359 |
+
idv_img_input = gr.Files(label='Insert individual images')
|
360 |
with gr.Column():
|
361 |
file_output = gr.File(label="Download ZIP")
|
362 |
with gr.Accordion(open=True, label="Gallery"):
|
|
|
366 |
with gr.Accordion(open=False, label="Preview"):
|
367 |
preview_output = gr.TextArea(label="Raw Text")
|
368 |
|
369 |
+
example_temp_html = TextProcessor.apply_style('demo.md', 'style.css')
|
370 |
+
|
371 |
+
examples = [
|
372 |
+
['http://example.com\nhttp://toscrape.com\nhttp://books.toscrape.com',
|
373 |
+
['img/number-1.png'],
|
374 |
+
['img/number-2.png']
|
375 |
+
]
|
376 |
+
]
|
377 |
+
inputs = [
|
378 |
+
url_input, cm_img_input, idv_img_input
|
379 |
+
]
|
380 |
+
gr.Examples(examples, inputs)
|
381 |
|
382 |
submit_button.click(
|
383 |
+
UrlProcessor.shorten_and_generate_qr,
|
384 |
+
inputs=[
|
385 |
+
access_token,
|
386 |
+
api_url,
|
387 |
+
url_input,
|
388 |
+
shorten_checkbox,
|
389 |
+
qr_checkbox,
|
390 |
+
cm_img_input,
|
391 |
+
idv_img_input],
|
392 |
outputs=[preview_output, gallery_output, file_output, json_output]
|
393 |
)
|
394 |
|
style.css
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
h1 {
|
2 |
+
font-weight: bold;
|
3 |
+
}
|
4 |
+
|
5 |
+
/* フォントとマージンの設定 */
|
6 |
+
p {
|
7 |
+
font-family: 'MS Mincho', serif;
|
8 |
+
margin: 10%;
|
9 |
+
}
|
10 |
+
|
11 |
+
/* 画像の透明度設定 */
|
12 |
+
img {
|
13 |
+
opacity: 0.5;
|
14 |
+
}
|
15 |
+
|
16 |
+
/* QRコードの位置設定 */
|
17 |
+
.qr {
|
18 |
+
position: absolute;
|
19 |
+
bottom: 10px;
|
20 |
+
right: 10px;
|
21 |
+
margin: 10%;
|
22 |
+
}
|
23 |
+
|
24 |
+
/* カードのサイズとグリッドレイアウト設定 */
|
25 |
+
.card {
|
26 |
+
width: 91mm;
|
27 |
+
height: 55mm;
|
28 |
+
display: grid;
|
29 |
+
grid-template-columns: 1fr;
|
30 |
+
grid-template-rows: 1fr;
|
31 |
+
align-items: center;
|
32 |
+
justify-items: center;
|
33 |
+
border: 1px solid black;
|
34 |
+
margin: 5mm;
|
35 |
+
position: relative;
|
36 |
+
}
|
37 |
+
|
38 |
+
/* 印刷時のA4用紙設定 */
|
39 |
+
@media print {
|
40 |
+
.a4 {
|
41 |
+
page-break-after: always;
|
42 |
+
width: 210mm;
|
43 |
+
height: 297mm;
|
44 |
+
display: grid;
|
45 |
+
grid-template-columns: repeat(2, 1fr);
|
46 |
+
grid-template-rows: repeat(5, 1fr);
|
47 |
+
gap: 0;
|
48 |
+
}
|
49 |
+
|
50 |
+
.card {
|
51 |
+
margin: 0;
|
52 |
+
border: none;
|
53 |
+
width: 100%;
|
54 |
+
height: 100%;
|
55 |
+
}
|
56 |
+
}
|