Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement HTML format #32

Merged
merged 6 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
name: Rust

on: [push, pull_request]
on:
push:
branches:
- master
paths:
- '.github/workflows/rust.yml'
- 'src/**/*.rs'
- 'tests/**/*.rs'
- 'Cargo.toml'
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- '**'
paths:
- '.github/workflows/rust.yml'
- 'src/**/*.rs'
- 'tests/**/*.rs'
- 'Cargo.toml'

jobs:
build:
runs-on: windows-latest
if: github.event.pull_request.draft == false

steps:
- uses: actions/checkout@v4
Expand Down
54 changes: 54 additions & 0 deletions src/formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use crate::{SysResult, Getter, Setter};
use crate::types::c_uint;

use core::num::NonZeroU32;

///A handle to a bitmap (HBITMAP).
pub const CF_BITMAP: c_uint = 2;
///A memory object containing a <b>BITMAPINFO</b> structure followed by the bitmap bits.
Expand Down Expand Up @@ -166,3 +168,55 @@ impl<T: AsRef<[u8]>> Setter<T> for Bitmap {
crate::raw::set_bitmap(data.as_ref())
}
}

#[derive(Copy, Clone)]
///HTML Foramt
///
///Reference: https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
pub struct Html(NonZeroU32);

impl Html {
#[inline(always)]
///Creates new instance, if possible
pub fn new() -> Option<Self> {
//utf-16 "HTML Format"
const NAME: [u16; 12] = [72, 84, 77, 76, 32, 70, 111, 114, 109, 97, 116, 0];
unsafe {
crate::raw::register_raw_format(&NAME).map(Self)
}
}

#[inline(always)]
///Gets raw format code
pub fn code(&self) -> u32 {
self.0.get()
}
}

impl Getter<alloc::vec::Vec<u8>> for Html {
#[inline(always)]
fn read_clipboard(&self, out: &mut alloc::vec::Vec<u8>) -> SysResult<usize> {
crate::raw::get_html(self.0.get(), out)
}
}

impl Getter<alloc::string::String> for Html {
#[inline(always)]
fn read_clipboard(&self, out: &mut alloc::string::String) -> SysResult<usize> {
crate::raw::get_html(self.0.get(), unsafe { out.as_mut_vec() })
}
}

impl<T: AsRef<str>> Setter<T> for Html {
#[inline(always)]
fn write_clipboard(&self, data: &T) -> SysResult<()> {
crate::raw::set_html(self.code(), data.as_ref())
}
}

impl From<Html> for u32 {
#[inline(always)]
fn from(value: Html) -> Self {
value.code()
}
}
65 changes: 65 additions & 0 deletions src/html.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use core::{cmp, fmt};

pub const SEP: char = ':';
pub const NEWLINE: &str = "\r\n";
pub const LEN_SIZE: usize = 10;
pub const VERSION: &str = "Version";
pub const START_FRAGMENT: &str = "StartFragment";
pub const END_FRAGMENT: &str = "EndFragment";
pub const START_HTML: &str = "StartHTML";
pub const END_HTML: &str = "EndHTML";
pub const BODY_HEADER: &str = "<html>\r\n<body>\r\n<!--StartFragment-->";
pub const BODY_FOOTER: &str = "<!--EndFragment-->\r\n</body>\r\n</html>";

pub struct LengthBuffer([u8; LEN_SIZE]);

impl LengthBuffer {
#[inline(always)]
pub const fn new() -> Self {
Self([b'0'; LEN_SIZE])
}

#[inline(always)]
pub const fn len(&self) -> usize {
self.0.len()
}

#[inline(always)]
pub const fn as_ptr(&self) -> *const u8 {
self.0.as_ptr()
}
}

impl AsRef<[u8]> for LengthBuffer {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}

impl fmt::Write for LengthBuffer {
fn write_str(&mut self, input: &str) -> fmt::Result {
debug_assert!(input.len() <= self.0.len());
let size = cmp::min(input.len(), self.0.len());

self.0[10-size..].copy_from_slice(&input.as_bytes()[..size]);

Ok(())
}
}

//Samples
//DATA=Version:0.9
//StartHTML:0000000187
//EndHTML:0000001902
//StartFragment:0000000223
//EndFragment:0000001866
//<html>
//<body>
//<!--StartFragment-->
//<table style="color: rgb(255, 255, 255); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><tbody></tbody></table>
//<!--EndFragment-->
//</body>
//</html>

//Version:0.9\r\nStartHTML:0000000105\r\nEndHTML:0000000187\r\nStartFragment:0000000141\r\nEndFragment:0000000151\r\n<html>\r\n<body>\r\n<!--StartFragment--><tr>1</tr><!--EndFragment-->\r\n</body>\r\n</html>
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ extern crate alloc;
mod sys;
pub mod types;
pub mod formats;
mod html;
pub mod raw;
#[cfg(feature = "monitor")]
pub mod monitor;
Expand Down
162 changes: 156 additions & 6 deletions src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ const CP_UTF8: DWORD = 65001;

use error_code::ErrorCode;

use core::{slice, mem, ptr, cmp};
use core::{slice, mem, ptr, cmp, str, hint};
use core::num::{NonZeroUsize, NonZeroU32};

use alloc::string::String;
use alloc::borrow::ToOwned;
use alloc::format;

use crate::{SysResult, formats};
use crate::{SysResult, html, formats};
use crate::utils::{unlikely_empty_size_result, RawMem};

#[inline(always)]
Expand Down Expand Up @@ -264,6 +264,156 @@ pub fn get_vec(format: u32, out: &mut alloc::vec::Vec<u8>) -> SysResult<usize> {
Ok(result)
}

///Retrieves HTML using format code created by `register_raw_format` or `register_format` with argument `HTML Format`
pub fn get_html(format: u32, out: &mut alloc::vec::Vec<u8>) -> SysResult<usize> {
let ptr = RawMem::from_borrowed(get_clipboard_data(format)?);

let result = unsafe {
let (data_ptr, _lock) = ptr.lock()?;
let data_size = GlobalSize(ptr.get()) as usize;

let data = str::from_utf8_unchecked(
slice::from_raw_parts(data_ptr.as_ptr() as *const u8, data_size)
);

let mut start_idx = 0usize;
let mut end_idx = data.len();
for line in data.lines() {
let mut split = line.split(html::SEP);
let key = match split.next() {
Some(key) => key,
None => hint::unreachable_unchecked(),
};
let value = match split.next() {
Some(value) => value,
//Reached HTML
None => break
};
match key {
html::START_FRAGMENT => match value.trim_start_matches('0').parse() {
Ok(value) => {
start_idx = value;
continue;
}
//Should not really happen
Err(_) => break,
},
html::END_FRAGMENT => match value.trim_start_matches('0').parse() {
Ok(value) => {
end_idx = value;
continue;
}
//Should not really happen
Err(_) => break,
},
_ => continue,
}
}

//Make sure HTML writer didn't screw up offsets of fragment
let size = match end_idx.checked_sub(start_idx) {
Some(size) => size,
None => return Err(ErrorCode::new_system(13)),
};
if size > data_size {
return Err(ErrorCode::new_system(13));
}

out.reserve(size);
let out_cursor = out.len();
ptr::copy_nonoverlapping(data.as_ptr().add(start_idx), out.spare_capacity_mut().as_mut_ptr().add(out_cursor) as _, size);
out.set_len(out_cursor + size);
size
};

Ok(result)
}

///Sets HTML using format code created by `register_raw_format` or `register_format` with argument `HTML Format`
pub fn set_html(format: u32, html: &str) -> SysResult<()> {
const VERSION_VALUE: &str = ":0.9";
const HEADER_SIZE: usize = html::VERSION.len() + VERSION_VALUE.len() + html::NEWLINE.len()
+ html::START_HTML.len() + html::LEN_SIZE + 1 + html::NEWLINE.len()
+ html::END_HTML.len() + html::LEN_SIZE + 1 + html::NEWLINE.len()
+ html::START_FRAGMENT.len() + html::LEN_SIZE + 1 + html::NEWLINE.len()
+ html::END_FRAGMENT.len() + html::LEN_SIZE + 1 + html::NEWLINE.len();
const FRAGMENT_OFFSET: usize = HEADER_SIZE + html::BODY_HEADER.len();

let total_size = FRAGMENT_OFFSET + html::BODY_FOOTER.len() + html.len();

let mut len_buffer = html::LengthBuffer::new();
let mem = RawMem::new_global_mem(total_size)?;

unsafe {
use core::fmt::Write;
let (ptr, _lock) = mem.lock()?;
let out = slice::from_raw_parts_mut(ptr.as_ptr() as *mut mem::MaybeUninit<u8>, total_size);

let mut cursor = 0;
macro_rules! write_out {
($input:expr) => {
let input = $input;
ptr::copy_nonoverlapping(input.as_ptr() as *const u8, out.as_mut_ptr().add(cursor) as _, input.len());
cursor += input.len();
};
}

write_out!(html::VERSION);
write_out!(VERSION_VALUE);
write_out!(html::NEWLINE);

let _ = write!(&mut len_buffer, "{:0>10}", HEADER_SIZE);
write_out!(html::START_HTML);
write_out!([html::SEP as u8]);
write_out!(&len_buffer);
write_out!(html::NEWLINE);

let _ = write!(&mut len_buffer, "{:0>10}", total_size);
write_out!(html::END_HTML);
write_out!([html::SEP as u8]);
write_out!(&len_buffer);
write_out!(html::NEWLINE);

let _ = write!(&mut len_buffer, "{:0>10}", FRAGMENT_OFFSET);
write_out!(html::START_FRAGMENT);
write_out!([html::SEP as u8]);
write_out!(&len_buffer);
write_out!(html::NEWLINE);

let _ = write!(&mut len_buffer, "{:0>10}", total_size - html::BODY_FOOTER.len());
write_out!(html::END_FRAGMENT);
write_out!([html::SEP as u8]);
write_out!(&len_buffer);
write_out!(html::NEWLINE);

//Verify StartHTML is correct
debug_assert_eq!(HEADER_SIZE, cursor);

write_out!(html::BODY_HEADER);

//Verify StartFragment is correct
debug_assert_eq!(FRAGMENT_OFFSET, cursor);

write_out!(html);

//Verify EndFragment is correct
debug_assert_eq!(total_size - html::BODY_FOOTER.len(), cursor);

write_out!(html::BODY_FOOTER);

//Verify EndHTML is correct
debug_assert_eq!(cursor, total_size);
}

if unsafe { !SetClipboardData(format, mem.get()).is_null() } {
//SetClipboardData takes ownership
mem.release();
Ok(())
} else {
Err(ErrorCode::last_system())
}
}

/// Copies raw bytes onto clipboard with specified `format`, returning whether it was successful.
///
/// This function empties the clipboard before setting the data.
Expand Down Expand Up @@ -653,12 +803,12 @@ pub fn set_file_list(paths: &[impl AsRef<str>]) -> SysResult<()> {
if unsafe { !SetClipboardData(formats::CF_HDROP, mem.get()).is_null() } {
//SetClipboardData now has ownership of `mem`.
mem.release();
return Ok(());
Ok(())
} else {
Err(ErrorCode::last_system())
}
return Err(ErrorCode::last_system());
}


///Enumerator over available clipboard formats.
///
///# Pre-conditions:
Expand Down Expand Up @@ -886,7 +1036,7 @@ pub fn register_format(name: &str) -> Option<NonZeroU32> {
register_raw_format(&buffer)
}
} else {
let mut buffer = mem::MaybeUninit::<[u16; 52]>::uninit();
let mut buffer = mem::MaybeUninit::<[u16; 52]>::zeroed();
let size = unsafe {
MultiByteToWideChar(CP_UTF8, 0, name.as_ptr() as *const _, name.len() as c_int, buffer.as_mut_ptr() as *mut u16, 51)
};
Expand Down
Loading